//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: ObjectQueryBuilder_1.cs
//
//  Description: Creates instances of ObjectQuery<T> classes.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Chili.Opf3.Query.OPathParser;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Query
{
	/// <summary>
	/// A class that creates an instance of an <see cref="ObjectQuery{T}">ObjectQuery</see> class.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class creates an instance of an <see cref="ObjectQuery{T}">ObjectQuery</see> class. It allows 
	/// to specify the different items that can be added to an ObjectQuery. For more information about the 
	/// ObjectQuery, please consult the documentation of that class.
	/// </para>
	/// <para>
	/// The class does also check if the added properties are valid for the given persistent object and if 
	/// the amount of opening parenthesis match the closing parenthesis. It is possible to call the 
	/// <see cref="ObjectQueryBuilder{T}.AppendProperty">AppendProperty</see> method without calling the
	/// <see cref="ObjectQueryBuilder{T}.AppendConcat">AppendConcat</see>: in that case the class uses the
	/// concat specified in the <see cref="ObjectQueryBuilder{T}.DefaultConcat">DefaultConcat</see> property
	/// to connect the two properties in the query.
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how to use ObjectQueryBuilder in your application.
	/// <code lang="CS">
	/// // ... Other code
	/// 
	/// ObjectQueryBuilder&lt;Call&gt; ob = new ObjectQueryBuilder&lt;Call&gt;();
	/// ob.AppendProperty("CallDuration", Comparisons.Equals, 1);
	/// ob.AppendProperty("CallName", Comparisons.Like, "%foo");
	/// ob.AppendConcat(Concats.Or);
	/// ob.AppendOpeningParenthesis();
	/// ob.AppendProperty("CallName", Comparisons.Equals, "Bar");
	/// ob.AppendClosingParenthesis();
	///
	/// ObjectSet&lt;Call&gt; os = context.GetObjectSet&lt;Call&gt;(ob.ToObjectQuery());
	/// 
	/// // ... Other code.
	/// </code>
	/// </example>
	public sealed class ObjectQueryBuilder<T>
	{
		private string _command = string.Empty;
		private List<object> _parameters = new List<object>();
		private Concats _defaultConcat = Concats.And;
		private string _sortBy;

		/// <summary>
		/// Appends a mapped persistent property to the query. The property is only added if the value is not null.
		/// </summary>
		/// <param name="propertyName">The name of the property that is added.</param>
		/// <param name="comparison">The <see cref="Comparisons">comparison</see> used to load all objects that match the value compared with the property.</param>
		/// <param name="value">The value of the property. All objects matching the value in combination with the comparison are loaded.</param>
		public void AppendPropertyIfParameterNotNull(string propertyName, Comparisons comparison, object value)
		{
			if (value == null)
				return;

			AppendProperty(propertyName, comparison, value);
		}

		/// <summary>
		/// Appends a mapped persistent property to the query.
		/// </summary>
		/// <param name="propertyName">The name of the property that is added.</param>
		/// <param name="comparison">The <see cref="Comparisons">comparison</see> used to load all objects that match the value compared with the property.</param>
		/// <param name="value">The value of the property. All objects matching the value in combination with the comparison are loaded.</param>
		public void AppendProperty(string propertyName, Comparisons comparison, object value)
		{
			CheckForDefaultConcat();
			CheckMappedProperty(propertyName);

			// if the command is empty we remove the beginning space.
			string formatString = string.IsNullOrEmpty(_command) ? "{0}{1} {2} " : "{0} {1} {2} ";

			if (value == null && (comparison == Comparisons.Equals ||comparison == Comparisons.NotEquals))
			{
				if (comparison == Comparisons.Equals)
					_command = string.Format(formatString, _command, propertyName, "is null");
				else
					_command = string.Format(formatString, _command, propertyName, "isnot null");
			}
			else
			{
				_command = string.Format(formatString, _command, propertyName, ComparisonToString(comparison)) + "{" + _parameters.Count + "} ";
				_parameters.Add(value);
			}
		}

		/// <summary>
		/// Appends a comparison between two properties to the query.
		/// </summary>
		/// <param name="propertyA">The first property that is compared with the second one.</param>
		/// <param name="comparison">The <see cref="Comparisons">comparison</see> used to compare both properties.</param>
		/// <param name="propertyB">The second property that is compared with the first one.</param>
		public void AppendPropertyComparison(string propertyA, Comparisons comparison, string propertyB)
		{
			CheckForDefaultConcat();
			CheckMappedProperty(propertyA);
			CheckMappedProperty(propertyB);

			_command = string.Format("{0} {1} {2} {3} ", _command, propertyA, ComparisonToString(comparison), propertyB);
		}

		/// <summary>
		/// Checks if the given property is mapped.
		/// </summary>
		/// <param name="propertyName">The name of the property.</param>
		private static void CheckMappedProperty(string propertyName)
		{
			TypeMapping m = TypeMapping.GetTypeMapping(typeof(T));
			if (m.Members.GetMemberInfoByName(propertyName) == null)
				throw new ArgumentException("The property \"" + propertyName + "\" is not a mapped property of the persistent object.");
		}

		/// <summary>
		/// Checks if to add a default concat between two items of the query.
		/// </summary>
		private void CheckForDefaultConcat()
		{
			string c = _command.Trim();
			if (_command == string.Empty)
				return;
			if (!c.EndsWith(Concats.And.ToString()) && !c.EndsWith(Concats.Or.ToString()) && !c.EndsWith("("))
				this.AppendConcat(this.DefaultConcat);
		}

		/// <summary>
		/// Converts a comparison to a string.
		/// </summary>
		/// <param name="comparison">The comparison that is converted.</param>
		private string ComparisonToString(Comparisons comparison)
		{
			switch (comparison)
			{
				case Comparisons.Equals:
					return "=";
				case Comparisons.Higher:
					return ">";
				case Comparisons.HigherEquals:
					return ">=";
				case Comparisons.Like:
					return "Like";
				case Comparisons.Lower:
					return "<";
				case Comparisons.LowerEquals:
					return "<=";
				case Comparisons.NotEquals:
					return "<>";
				default:
					throw new NotSupportedException("Comparison not supported by ObjectQueryBuilder.");
			}
		}

		/// <summary>
		/// Appends a concat to the query.
		/// </summary>
		/// <param name="concat">The concat that is going to be added to the query.</param>
		public void AppendConcat(Concats concat)
		{
			_command += concat.ToString();
		}

		/// <summary>
		/// Appends an opening parenthesis to the query.
		/// </summary>
		public void AppendOpeningParenthesis()
		{
			CheckForDefaultConcat();

			_command += " (";
		}

		/// <summary>
		/// Appends a closing parenthesis to the query.
		/// </summary>
		public void AppendClosingParenthesis()
		{
			_command += ")";
		}

		/// <summary>
		/// Appends a sort to the query.
		/// </summary>
		/// <param name="propertyName">The property name to sort the query by.</param>
		/// <param name="sortDirection">The direction of the sort.</param>
		public void AppendSort(string propertyName, SortDirections sortDirection)
		{
			CheckMappedProperty(propertyName);

			if (_sortBy == null)
				_sortBy = string.Format("SortBy {0} {1}", propertyName, (sortDirection == SortDirections.Ascending ? "Asc" : "Desc"));
			else
				_sortBy += string.Format(", {0} {1}", propertyName, (sortDirection == SortDirections.Ascending ? "Asc" : "Desc"));
		}

		/// <summary>
		/// Returns whether the current instance of the class is empty.
		/// </summary>
		public bool IsEmpty
		{
			get { return (_command == string.Empty && _parameters.Count == 0 && _sortBy == null) ; }
		}

		/// <summary>
		/// Clears the current content of the <see cref="ObjectQueryBuilder{T}">ObjectQueryBuilder</see>.
		/// </summary>
		public void Clear()
		{
			_command = string.Empty;
			_parameters.Clear();
			_sortBy = null;
		}

		/// <summary>
		/// Specifies the default concat that is used if <see cref="ObjectQueryBuilder{T}.AppendProperty">AppendProperty</see> is 
		/// called more then one time without calling <see cref="ObjectQueryBuilder{T}.AppendConcat">AppendConcat</see>.
		/// </summary>
		public Concats DefaultConcat
		{
			get { return _defaultConcat; }
			set { _defaultConcat = value; }
		}

		/// <summary>
		/// Converts the value to a instance of the <see cref="ObjectQuery{T}">ObjectQuery</see> Class.
		/// </summary>
		/// <returns></returns>
		public ObjectQuery<T> ToObjectQuery()
		{
			if (Regex.Matches(_command, @"\(").Count != Regex.Matches(_command, @"\)").Count)
				throw new OPathException("Closing parenthesis don't fit amount of opening.", _command, _command);

			object[] p = null;
			if (_parameters.Count > 0)
				p = _parameters.ToArray();

			string c = _command;
			if (_sortBy != null)
				c += _sortBy;

			return new ObjectQuery<T>(c, p);
		}
	}
}
